from dataclasses import dataclass
from datetime import date, datetime
from typing import Tuple, Dict, TypeVar, Type, Optional, List, Union

from emi import io
from emi.bar.data import BarBase, Interval, BarData


class Dimen(object):

    """
    维度:由name, features组成一个维度,其中fature必须是由数字串组成。
    """
    __slots__ = '_value', '_name', '_feature'

    @property
    def value(self):
        return self._value

    def get_name(self):
        """
        返回Name值
        :return:
        """
        return self._name

    def get_feature(self,as_str:bool = False)->Union[Tuple[int],str]:
        """
        返回编码值
        :return:
        """
        if as_str:
            return Dimen.get_feature_str(self._feature)
        return self._feature

    @staticmethod
    def get_feature_str(feature:Union[Tuple[int],List[int]])->str:
        """
        返回编码值
        :return:
        """
        if feature:
            return  "_".join([str(v) for v in feature])
        return ""

    def __new__(cls, name:str=None, feature:Union[List[int],Tuple[int]]=None):
        """Constructor.

        Arguments:

        name, codes (required, base 1)
        """
        self = object.__new__(cls)
        if name is None:
            name = ""
        else:
            _index = name.find(":")
            if _index >= 0:
                        # More informative error message.
                        raise ValueError(
                            "name can't contains ':' char")
            name = name.strip()

        if feature is None:
            feature = ()
        self.__set_state(name,feature)
        return self

    def __str__(self, *args, **kwargs): # real signature unknown
        """ Return str(self). """
        return self._value

    def __set_state(self,name,codes):
        self._name = name
        self._feature = codes
        if codes:
            code_str = Dimen.get_feature_str(self._feature)
            self._value = ":".join([name,code_str])
        else:
            self._value = name

    @staticmethod
    def parse(dimen_value:str):
        """
        格式：
            name:数字1-数字2-数字3
            name:
            数字1-数字2-数字3
        """
        name_codes_split_index = dimen_value.find(":")
        if name_codes_split_index < 0:
            ## 只能name值。
            name = dimen_value
            return Dimen(name,feature=None)
        else:
            name = dimen_value[0:name_codes_split_index]
            codes_str = dimen_value[name_codes_split_index+1:]
            if len(codes_str) > 0:
                codes = [int(v) for v in codes_str.split("_")]
            else:
                codes = ()
            return Dimen(name=name,feature=codes)

    def __eq__(self, other: object) -> bool:
        if isinstance(other, self.__class__):
            return self.value == other.value
        else:
            return False

    def __hash__(self) -> int:
        return hash(self.value)


@dataclass
class BarItem:
    """
    只支持简单类型。
    """
    pass

BarItemType = TypeVar("BarItemType")

@dataclass
class BarX(BarData):
    """
    BarX是一个加工后的BarData数据。
    方便做量化、预测的行情数据对象。含有几个特点：
    + dimen：  特征值，量化数据建立的基础
    + extra: 额外属性。
    + BarItem: 各种附加值，比如： 指标值、拐点值，状态值。
    """
    dimen_value:str = None  ## 维度值
    _item_bytes:bytes = None

    @property
    def extra(self)->Dict:
        if self._extra_bytes is not None:
            _extra,_offset = io.read_dict(self._extra_bytes,0)
            self._extra_bytes = None
            setattr(self,"_extra",_extra)
            return _extra
        _extra = getattr(self,"_extra",None)
        if _extra is None:
            _extra= {}
            setattr(self,"_extra",_extra)
        return _extra

    def load_item(self,name:str,item_type:Type[BarItemType]=BarItem)->Optional[BarItemType]:
        """
        返回一个Item内容。是一个BarItem类型对象。
        :param name:
        :param item_type:
        :return:
        """
        _data_map_:Dict =  self._item_map().get(name)
        if not _data_map_:
            return None
        bar_item = item_type()
        for name,value in _data_map_.items():
            setattr(bar_item,name,value)
        return bar_item

    def put_item(self,name:str,item:BarItem):
        if not isinstance(item,BarItem):
            raise RuntimeError("item must be a instance of BarItem")
        _item_map = self._item_map()
        _item_map[name] = item.__dict__

    def _item_map(self)->Dict:
        if self._item_bytes is not None:
            _item_data,_offset = io.read_dict(self._item_bytes,0)
            self._item_bytes = None
            setattr(self,"_item_data",_item_data)
            return _item_data
        _item_data = getattr(self,"_item_data",None)
        if _item_data is None:
            _item_data= {}
            setattr(self, "_item_data", _item_data)
        return _item_data

    _lazy_load_fun = None  ## 延迟加载:

    def set_details(self, details: List['BarX'], merge=True):
        setattr(self, "_details", details)
        if merge and details:
            size = len(details)
            self.open = details[0].open
            self.close = details[0].close
            self.high = details[0].high
            self.low = details[0].low
            self.pre_close = details[0].pre_close
            self.volume = details[0].volume
            self.amount = details[0].amount
            bar = self
            for i in range(1, size):
                assert details[i - 1].datetime.timestamp() < details[i].datetime.timestamp()
                bar.high = max(details[i].high, bar.high)
                bar.low = min(details[i].low, bar.low)
                bar.close = details[i].close
                bar.volume += details[i].volume
                bar.amount += details[i].amount

    @property
    def details(self) -> Optional[List['BarX']]:
        if self._lazy_load_fun is not None:
            details = self._lazy_load_fun()
            self._lazy_load_fun = None
            setattr(self, "_details", details)
            return details
        return getattr(self, "_details", None)



if __name__ == "__main__":
    dimen = Dimen.parse("lb>0.5:0")
    dimen2 = Dimen.parse(":1_2_3_4_5")
    dimen3 = Dimen.parse("twee:1_2_3_4_5")
    dimen4 = Dimen.parse("")
    dimen5 = Dimen.parse(":")

    barX = BarX(symbol="wer",interval=Interval.DAILY,datetime=datetime.now())

    @dataclass
    class D(BarItem):
        e:int = 1
        f:int = 2

    d = D()
    print(f"{d.__dict__}")
    assert barX.load_item("d") is None
    barX.put_item("d",d)

    d.e = 34
    assert barX.load_item("d",D).e == 34

    barX.load_item("d", D).e = 5
    assert d.e ==34
    assert barX.load_item("d", D).e == 34


    def assert_ok(dimen:Dimen):
        d2 = Dimen.parse(dimen.value)
        assert dimen.value == d2.value
        assert dimen == d2
        assert dimen is not d2

        print(f"i: {d2}")


    ## 老版本格式
    assert_ok(dimen)
    assert_ok(dimen2)
    assert_ok(dimen3)
    assert_ok(dimen4)
    assert_ok(dimen5)

